Skip to content

Fix native fullscreen unreachable on the main window (#5933) - #6830

Merged
austinywang merged 3 commits into
mainfrom
issue-5933-native-fullscreen-unreachable-on-multi-monito
Jun 29, 2026
Merged

austinywang merged 3 commits into
mainfrom
issue-5933-native-fullscreen-unreachable-on-multi-monito

Conversation

@austinywang

@austinywang austinywang commented Jun 26, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #5933.

Problem

On macOS 26 (Tahoe), cmux's main window cannot enter native fullscreen by any route:

  • Command Palette Toggle Full Screen → no-op
  • ⌃⌘F → no-op
  • Green traffic-light button → only zooms/maximizes; never moves to its own fullscreen Space

All three entrypoints resolve the right CmuxMainWindow and call toggleFullScreen(_:), so window targeting is fine. The symptom set (toggle is a no-op and the green button zooms instead of fullscreening) is the textbook signature of a window that is not fullscreen-capable.

Root cause

CmuxMainWindow is created programmatically (never from a nib), so it cannot inherit fullscreen capability from Interface Builder. It never declared .fullScreenPrimary and relied on AppKit implicitly granting fullscreen to a resizable, titled window.

That implicit grant is not reliable across macOS versions. Verified empirically on macOS 26.5 (Tahoe): a window created with cmux's exact style mask ([.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView]) reports collectionBehavior.rawValue == 0 — i.e. it carries none of .fullScreenPrimary / .fullScreenAuxiliary / .fullScreenNone, and AppKit does not treat it as fullscreen-capable. This matches CLAUDE.md's standing warning that AppKit semantics change silently between macOS majors, so implicit defaults must not be assumed stable.

Fix

Declare .fullScreenPrimary explicitly in CmuxMainWindow's initializer, via a pure, unit-testable canonicalCollectionBehavior(_:) helper. The helper:

  • inserts .fullScreenPrimary so native fullscreen is always reachable,
  • strips any stale .fullScreenNone (mutually exclusive with primary),
  • preserves unrelated bits, so it composes with the temporary .fullScreenDisallowsTiling opt-out the window factory layers on when spawning a window out of an existing fullscreen Space.

It is idempotent and a no-op where AppKit would have granted fullscreen anyway, so it cannot regress setups that already worked.

Tests

Two-commit red/green:

  1. Commit 1 adds CmuxMainWindowFullScreenCapabilityTests (wired into project.pbxproj). The window-instantiation test fails on current code because the default collection behavior lacks .fullScreenPrimary.
  2. Commit 2 adds the fix, turning the test green, plus pure-function tests for the canonicalCollectionBehavior(_:) contract (adds primary, drops stale none, preserves unrelated bits, idempotent).

Scope note: sleep/wake size shrink

The issue also mentions the window shrinking after display sleep/wake. The reporter is on v0.64.14; the sleep/wake reposition fix (#6305, CmuxMainWindow.constrainFrameRect) landed in v0.64.17 / nightly and is already on main, which lines up with the reporter's note that the bug "does not reproduce on NIGHTLY." This PR targets the fullscreen-capability defect, which is the part still present on main and is not addressed anywhere else. The fullscreen change is orthogonal to and composes with the existing constrain-frame logic.

Localization

No user-facing strings added or changed — the "Toggle Full Screen" command and its command.toggleFullScreen.* strings already exist in Localizable.xcstrings. Change is window-capability code + tests + pbxproj wiring only.

🤖 Generated with Claude Code


View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.


Summary by cubic

Make the main window explicitly fullscreen-capable so native fullscreen works again via Toggle Full Screen, ⌃⌘F, and the green button on macOS 26 and multi-monitor setups. Fixes #5933.

  • Bug Fixes
    • Add canonicalCollectionBehavior(_:) and apply it in CmuxMainWindow init to insert .fullScreenPrimary, remove .fullScreenNone, and preserve other behavior bits.
    • Add tests for fullscreen capability and the helper’s contract (adds primary, drops none, preserves bits, idempotent), now using Swift Testing (@Suite, @Test).

Written for commit 4199e20. Summary will update on new commits.

Review in cubic

cmux and others added 2 commits June 26, 2026 01:01
cmux creates its main window programmatically and never declares
.fullScreenPrimary, relying on AppKit's implicit grant of fullscreen
capability to a resizable, titled window. On macOS 26 (Tahoe) a
freshly-created CmuxMainWindow reports an empty collection behavior
(rawValue == 0) and AppKit does not treat it as fullscreen-capable, so
Toggle Full Screen / ⌃⌘F / the green traffic-light button all fail to
enter a native fullscreen Space (the green button only zooms).

This test asserts the main window declares .fullScreenPrimary. It fails
on current code (no fix yet) and will pass once the window declares the
capability explicitly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fixes #5933.

CmuxMainWindow is created programmatically and never declared
`.fullScreenPrimary`, so it relied on AppKit implicitly granting
fullscreen capability to a resizable, titled window. On macOS 26 (Tahoe)
that implicit grant does not happen — a freshly-created window reports an
empty collection behavior (`rawValue == 0`) and AppKit does not treat it
as fullscreen-capable. As a result Toggle Full Screen, ⌃⌘F, and the green
traffic-light button all fail to enter a native fullscreen Space (the
green button only zooms), which is what multi-monitor Tahoe users hit.

Declare `.fullScreenPrimary` explicitly in the window initializer via a
pure, unit-testable `canonicalCollectionBehavior(_:)` helper so native
fullscreen is reachable regardless of the OS's implicit default. The
helper also strips any stale `.fullScreenNone` and preserves unrelated
bits, so it composes with the temporary `.fullScreenDisallowsTiling`
opt-out the window factory applies when spawning a window out of an
existing fullscreen Space.

This makes the regression test added in the previous commit pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 26, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cmux Canceled Canceled Jun 26, 2026 12:39pm
cmux-staging Building Building Preview, Comment Jun 26, 2026 12:39pm

@coderabbitai

coderabbitai Bot commented Jun 26, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

@austinywang, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 39 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3ffc6b42-354d-4885-a9e7-31fb831bfc8b

📥 Commits

Reviewing files that changed from the base of the PR and between abbf278 and 4199e20.

📒 Files selected for processing (3)
  • Sources/App/CmuxMainWindow.swift
  • cmux.xcodeproj/project.pbxproj
  • cmuxTests/CmuxMainWindowFullScreenCapabilityTests.swift
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-5933-native-fullscreen-unreachable-on-multi-monito

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Jun 26, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes native fullscreen being unreachable on the CmuxMainWindow by explicitly declaring .fullScreenPrimary in the window's designated initializer, replacing a reliance on AppKit's implicit grant that stopped working on macOS 26 (Tahoe).

  • CmuxMainWindow: Adds an override init that calls Self.canonicalCollectionBehavior(collectionBehavior) after super.init, inserting .fullScreenPrimary and removing any stale .fullScreenNone; adds the standard @available(*, unavailable) required init?(coder:) guard since the window is never loaded from a nib.
  • canonicalCollectionBehavior: Pure nonisolated static helper that preserves all unrelated bits (including .fullScreenDisallowsTiling the window factory may add later), making the change idempotent and composable.
  • Tests: New CmuxMainWindowFullScreenCapabilityTests suite covers the window-level declaration and all four contracts of the helper (adds primary, drops stale none, preserves unrelated bits, idempotent), wired into the test target via project.pbxproj.

Confidence Score: 5/5

Safe to merge — the change is a minimal, focused init override with no observable side effects on setups where AppKit already granted fullscreen capability.

The production change is a single idempotent write to collectionBehavior in the window's designated initializer. The helper is pure, operates only on value types, and is covered by four targeted tests. No actor isolation, blocking, or global-state concerns are introduced; existing constrain-frame and tiling-opt-out logic is unaffected.

No files require special attention.

Important Files Changed

Filename Overview
Sources/App/CmuxMainWindow.swift Adds override init to explicitly declare .fullScreenPrimary via a pure nonisolated static canonicalCollectionBehavior helper; also adds the unavailable required init?(coder:) guard. Change is focused, idempotent, and composes correctly with post-init factory behavior.
cmuxTests/CmuxMainWindowFullScreenCapabilityTests.swift New test suite covering window-level declaration of .fullScreenPrimary and all contracts of canonicalCollectionBehavior (adds primary, drops stale none, preserves unrelated bits, idempotent). Correctly uses @mainactor, sets isReleasedWhenClosed = false, and defers cleanup.
cmux.xcodeproj/project.pbxproj Wires CmuxMainWindowFullScreenCapabilityTests.swift into the test target with the expected PBXBuildFile, PBXFileReference, and Sources phase entries. No unrelated changes.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["CmuxMainWindow.init(contentRect:styleMask:backing:defer:)"]
    B["super.init → AppKit sets collectionBehavior\n(rawValue == 0 on macOS 26 Tahoe)"]
    C["canonicalCollectionBehavior(collectionBehavior)"]
    D{"contains .fullScreenNone?"}
    E["remove(.fullScreenNone)"]
    F["insert(.fullScreenPrimary)"]
    G["return updated CollectionBehavior"]
    H["self.collectionBehavior = result\n(preserves any other bits already set)"]
    I["Window factory may later insert\n.fullScreenDisallowsTiling — preserved\nbecause insert is additive"]

    A --> B
    B --> C
    C --> D
    D -- yes --> E --> F
    D -- no --> F
    F --> G --> H --> I
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A["CmuxMainWindow.init(contentRect:styleMask:backing:defer:)"]
    B["super.init → AppKit sets collectionBehavior\n(rawValue == 0 on macOS 26 Tahoe)"]
    C["canonicalCollectionBehavior(collectionBehavior)"]
    D{"contains .fullScreenNone?"}
    E["remove(.fullScreenNone)"]
    F["insert(.fullScreenPrimary)"]
    G["return updated CollectionBehavior"]
    H["self.collectionBehavior = result\n(preserves any other bits already set)"]
    I["Window factory may later insert\n.fullScreenDisallowsTiling — preserved\nbecause insert is additive"]

    A --> B
    B --> C
    C --> D
    D -- yes --> E --> F
    D -- no --> F
    F --> G --> H --> I
Loading

Reviews (2): Last reviewed commit: "Use Swift Testing for the fullscreen cap..." | Re-trigger Greptile

Aziz test-framework policy: new, non-UI test files should use Swift
Testing (XCTest stays for cmuxUITests only). Convert the new
CmuxMainWindowFullScreenCapabilityTests from XCTestCase/XCTAssert to a
@mainactor @suite with @Test/#expect. No behavior change — same
window-instantiation assertion plus the four pure-helper contracts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

This branch was successfully deployed

1 active deployment
Preview – cmux — 4199e20a Deployed Jun 26, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Native fullscreen unreachable on multi-monitor setup (Toggle Full Screen / ⌃⌘F / green button all no-op) + window shrinks after sleep/wake

1 participant